
import java.io.FileNotFoundException;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Hashtable;

import alphas.a2.util.ClientAuth;
import alphas.a2.util.IClientAuth;

/**
 * Class ConnectionHandler ...
 *
 * @author Ricardo Lima
 * @version 1.0, 14/Out/2008 18:21:16
 */
public class ConnectionHandler extends Thread {

    //supported evtypes
    private static String LOGIN = "login";
    private static String LOGOUT = "logout";
    private static String STOP = "stop";
    private static String ADD_ORDER = "add_order";
    private static String SHIPPED = "shipped";
    private Socket s;
    private LogServer server;
    private int thread_id;
    private LogWritter log;

    private boolean stopSignal = false;

    /**
     * Constructor ConnectionHandler creates a new ConnectionHandler instance.
     *
     * @param s of type Socket
     * @param server of type GwRTC
     */
    public ConnectionHandler(Socket s, LogServer server, LogWritter log) {
        this.s = s;
        this.server = server;
        this.thread_id = (int) (Math.random() * 10000 / 100);

        this.log = log;
    }

    /**
     * Method run ...
     */
    public void run() {
        try {
            this.startWorking();
        } catch (Exception e) {
            this.debug("Unexpected error processing messages");
            e.printStackTrace();
        }

        this.server.free_thread();

        if (!this.s.isClosed()) {
            try {
                this.s.close();
            } catch (IOException e) {
                this.debug("Unexpected error processing messages");
                e.printStackTrace();
            }
        }

        if (this.stopSignal) {
            this.server.stop();
        }
    }

    /**
     * Method startWorking ...
     * @throws IOException when
     */
    public void startWorking() throws IOException {

        BufferedReader br = new BufferedReader(new InputStreamReader(this.s.getInputStream()));
        OutputStream out = this.s.getOutputStream();

        while (this.server.isRunning()) {

            if (this.stopSignal) {
                s.close();
                break;
            }

            if (this.s.isClosed()) {
                break;
            }

            String request;
            try {
                request = br.readLine();
            } catch (SocketTimeoutException e) {
                continue;
            }

            if (request == null) {
                break;
            }

            this.debug("REQUEST -> " + request);
            String resp = this.processRequest(request);
            this.debug("RESPONSE <- " + resp);

            out.write((resp + "\n").getBytes());
            out.flush();
        }
    }

    /**
     * Method debug ...
     *
     * @param s of type String
     */
    public void debug(String s) {
        System.out.println("[" + this.thread_id + "] " + s);
    }

    /**
     * Decode a logLine
     *
     * @param logLine the line received
     * @return an Hashtable containing the keys and values decoded
     */
    private Hashtable<String, String> decode(String logLine) {

        Hashtable<String, String> logHash = new Hashtable<String, String>();

        String key, value;

        if (logLine.indexOf("=") != -1) {
            String args[] = logLine.split(",");

            for (String arg : args) {

                key = arg.substring(0, arg.indexOf("=")).toLowerCase();
                value = arg.substring(arg.indexOf("=") + 1);

                // retirar o \n
                if (value.indexOf("\n") != -1) {
                    value = value.substring(0, value.indexOf("\n"));
                }
                logHash.put(key, value);
            }
        }
        return logHash;
    }

    // Util
    public static String urlEncoder(String msg) {
        try {
            return URLEncoder.encode(msg, "UTF-8");
        } catch (UnsupportedEncodingException ue) {
            return null;
        }
    }

    public static String urlDecoder(String encodedString) {
        try {
            return URLDecoder.decode(encodedString, "UTF-8");
        } catch (UnsupportedEncodingException ue) {
            return null;
        }
    }

    private String encodeResponse(String msg) {
        return "message=" + urlEncoder(msg);
    }

    private boolean validateOP(Hashtable<String, String> logHash) {
        String user = logHash.get("user");
        String pass = logHash.get("pass");

        if (user == null || pass == null) {
            return false;
        }

        user = urlDecoder(user);
        pass = urlDecoder(pass);
        
        IClientAuth clientAuth = new ClientAuth(server.getDbhost(), server.getDbport());                

        return clientAuth.hasOperatorPermission(user, pass);
    }

    private boolean validateSH(Hashtable<String, String> logHash) {
        String user = logHash.get("user");
        String pass = logHash.get("pass");

        if (user == null || pass == null) {
            return false;
        }

        user = urlDecoder(user);
        pass = urlDecoder(pass);

        IClientAuth clientAuth = new ClientAuth(server.getDbhost(), server.getDbport());

        return clientAuth.hasShippingPermission(user, pass);
    }

    private String access(Hashtable<String, String> logHash, String evtype) {
        String ts = logHash.get("ts");
        if (ts == null) {
            return "missing ts";
        }

        if (!this.validateOP(logHash)) {
            return "invalid user or password";
        }

        String user = urlDecoder(logHash.get("user"));

        String logLine = "ts="+ts+",user="+user+",evtype="+evtype;
        try {
            this.log.logAccess(logLine);
        } catch (IOException ex) {
            System.out.print("ERROR logging: "+logLine);
            return "error writting log";
        }

        return "ok";
    }

    private String addOrder(Hashtable<String, String> logHash) {
        String ts = logHash.get("ts");
        if (ts == null) {
            return "missing ts";
        }

        String order_id = logHash.get("order_id");
        if (order_id == null) {
            return "missing order_id";
        }

        if (!this.validateOP(logHash)) {
            return "invalid user or password";
        }

        String user = urlDecoder(logHash.get("user"));

        String logLine = "ts="+ts+",user="+user+",evtype="+ADD_ORDER+",order_id="+order_id;
        try {
            this.log.logOrder(logLine);
        } catch (IOException ex) {
            System.out.print("ERROR logging: "+logLine);
            return "error writting log";
        }

        return "ok";
    }

    private String shipped(Hashtable<String, String> logHash) {
        String ts = logHash.get("ts");
        if (ts == null) {
            return "missing ts";
        }

        String order_id = logHash.get("order_id");
        if (order_id == null) {
            return "missing order_id";
        }
        
        if (!this.validateSH(logHash)) {
            return "invalid user or password";
        }

        String logLine = "ts="+ts+",evtype="+SHIPPED+",order_id="+order_id;
        try {
            this.log.logShipment(logLine);
        } catch (IOException ex) {
            System.out.print("ERROR logging: "+logLine);
            return "error writting log";
        }

        return "ok";
    }

    private String stop(Hashtable<String, String> logHash) {
            if (!this.validateOP(logHash)) {
                return "invalid user or password";
            }
            this.stopSignal = true;
            return "ok";
    }

    // request processing ///////////////////////////////
    public String processRequest(String line) throws FileNotFoundException, IOException {

        Hashtable<String, String> logHash = this.decode(line);

        String evtype = logHash.get("evtype");
        if (evtype == null) {
            return this.encodeResponse("missing evtype");
        }

        if (evtype.equalsIgnoreCase(LOGIN)) {
            return this.encodeResponse(this.access(logHash, evtype));
        } else if (evtype.equalsIgnoreCase(LOGOUT)) {
            return this.encodeResponse(this.access(logHash, evtype));
        } else if (evtype.equalsIgnoreCase(ADD_ORDER)) {
            return this.encodeResponse(this.addOrder(logHash));
        } else if (evtype.equalsIgnoreCase(SHIPPED)) {
            return this.encodeResponse(this.shipped(logHash));
        } else if (evtype.equalsIgnoreCase(STOP)) {
            return this.encodeResponse(this.stop(logHash));
        } else {
            return this.encodeResponse("invalid evtype: " + evtype);
        }
    }
}
